﻿using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Xml;
using System.Xml.Schema;
#if NETFX35RTM
using System.Runtime.Serialization.Json;
#endif

namespace dasBlog.Storage
{
    internal static class SerializationTools<T> where T : class, new() 
    {
        static private DataContractSerializer _serializer = new DataContractSerializer(typeof(T));
        static private DataContractJsonSerializer _jsonSerializer = new DataContractJsonSerializer(typeof(T));
        static private DataContractSerializer _listSerializer = new DataContractSerializer(typeof(T[]),"list","");
        static private DataContractJsonSerializer _listJsonSerializer = new DataContractJsonSerializer(typeof(T[]), "list");
        static private XmlDictionary _dict = new XmlDictionary();
        static private XmlDictionaryReaderQuotas _quotas = new XmlDictionaryReaderQuotas();
        static private XmlSchemaSet _schemas;
        static private XmlDocument _factory = new XmlDocument();
        static private Dictionary<string, string> _schemaNames;
        static private Dictionary<string, string> _schemaQueries;
        static private Dictionary<string, XmlSchemaSet> _baseUriSchemaSet;
        static private string _primarySchemaTns;
        static private bool _initialized=false;

        class TBodyWriter : BodyWriter
        {
            T[] _enm = null;
            T _obj = null;
            WebMessageFormat _fmt;

            public TBodyWriter(T obj, WebMessageFormat format)
                : base(false)
            {
                _obj = obj;
                _fmt = format;
            }

            public TBodyWriter(T[] enm, WebMessageFormat format)
                : base(false)
            {
                _enm = enm;
                _fmt = format;
            }

            protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
            {
                if (_enm != null)
                {
                    if (_fmt == WebMessageFormat.Xml)
                        _listSerializer.WriteObject(writer, _enm);
                    else
                        _listJsonSerializer.WriteObject(writer, _enm);
                    
                }
                else if (_obj != null)
                {
                    if (_fmt == WebMessageFormat.Xml)
                        _serializer.WriteObject(writer, _obj);
                    else
                        _jsonSerializer.WriteObject(writer, _obj);
                }
                writer.Flush();
            }
        }


        static SerializationTools()
        {
            
        }

        private static void EnsureSchemaInit()
        {
            if (!_initialized)
            {
                XsdDataContractExporter _xe = new XsdDataContractExporter();
                _xe.Export(typeof(T));
                _schemas = _xe.Schemas;
                _primarySchemaTns = _xe.GetSchemaTypeName(typeof(T)).Namespace;
                _schemaNames = new Dictionary<string, string>();
                _schemaQueries = new Dictionary<string, string>();
                _baseUriSchemaSet = new Dictionary<string, XmlSchemaSet>();
                int count = 1;
                foreach (XmlSchema schema in _schemas.Schemas())
                {
                    _schemaNames.Add(schema.TargetNamespace, "xsd" + count.ToString());
                    _schemaQueries.Add("xsd" + count.ToString(), schema.TargetNamespace);
                    count++;
                }
                _initialized = true;
            }
        }

    

        internal static BodyWriter ToBodyWriter(T that, WebMessageFormat format)
        {
            return new TBodyWriter(that, format);
        }

        internal static BodyWriter ToBodyWriter(T[] that, WebMessageFormat format)
        {
            return new TBodyWriter(that, format);
        }

        internal static XmlElement ToXmlElement(IEnumerable<T> objects)
        {
            return _factory.ReadNode(SerializeList(objects, WebMessageFormat.Xml)) as XmlElement;
        }

        internal static XmlElement ToXmlElement(T that)
        {
            return _factory.ReadNode(Serialize(that, WebMessageFormat.Xml)) as XmlElement;
        }

        internal static XmlElement ToXmlElement(IEnumerable<T> objects, WebMessageFormat format)
        {
            return _factory.ReadNode(SerializeList(objects, format)) as XmlElement;
        }

        internal static XmlElement ToXmlElement(T that, WebMessageFormat format)
        {
            return _factory.ReadNode(Serialize(that, format)) as XmlElement;
        }

        internal static XmlReader Serialize(T that)
        {
            return Serialize(that, WebMessageFormat.Xml);
        }

        internal static XmlReader Serialize(T that, WebMessageFormat format) 
        {
            var memStream = new MemoryStream();
            var writer = XmlDictionaryWriter.CreateBinaryWriter(memStream, _dict);
            if (format == WebMessageFormat.Xml)
                _serializer.WriteObject(writer, that);
            else
                _jsonSerializer.WriteObject(writer, that);

            writer.Flush();
            memStream.Position = 0;
            return XmlDictionaryReader.CreateBinaryReader(memStream, _dict, _quotas);
        }

        internal static XmlReader SerializeList(IEnumerable<T> objects)
        {
            return SerializeList(objects, WebMessageFormat.Xml);
        }

        internal static XmlReader SerializeList(IEnumerable<T> objects, WebMessageFormat format)
        {
            T[] data = new List<T>(objects).ToArray();

            var memStream = new MemoryStream();
            var writer = XmlDictionaryWriter.CreateBinaryWriter(memStream, _dict);
                
            if (format == WebMessageFormat.Xml)
                    _listSerializer.WriteObject(writer, data);
                else
                    _listJsonSerializer.WriteObject(writer, data);
                
            writer.Flush();
            memStream.Position = 0;
            return XmlDictionaryReader.CreateBinaryReader(memStream, _dict, _quotas);
        }

        internal static T Deserialize(Message msg)
        {
            if (msg == null || msg.IsEmpty)
                return null;

            return Deserialize(msg.GetReaderAtBodyContents(), WebMessageFormat.Xml);
        }

        internal static T Deserialize(Message msg, WebMessageFormat format)
        {
            if (msg == null || msg.IsEmpty)
                return null;

            return Deserialize(msg.GetReaderAtBodyContents(), format);
        }

        internal static IList<T> DeserializeList(Message msg)
        {
            if (msg == null || msg.IsEmpty)
                return new List<T>();

            return DeserializeList(msg.GetReaderAtBodyContents(), WebMessageFormat.Xml);
        }

        internal static IList<T> DeserializeList(Message msg, WebMessageFormat format)
        {
            if (msg == null || msg.IsEmpty)
                return new List<T>();

            return DeserializeList(msg.GetReaderAtBodyContents(), format);
        }

        internal static T Deserialize(XmlReader reader)
        {
            return Deserialize(reader, WebMessageFormat.Xml);
        }

        internal static T Deserialize(XmlReader reader, WebMessageFormat format)
        {
            if (reader == null)
                return null;

            if ( format == WebMessageFormat.Xml)
                return _serializer.ReadObject(reader) as T;
            else
                return _jsonSerializer.ReadObject(reader) as T;
        }

        internal static IList<T> DeserializeList(XmlReader reader)
        {
            return DeserializeList(reader, WebMessageFormat.Xml);
        }

        internal static IList<T> DeserializeList(XmlReader reader, WebMessageFormat format)
        {
            List<T> list;

            if (reader == null)
                return new List<T>();

            if ( format == WebMessageFormat.Xml)
                list = new List<T>(_listSerializer.ReadObject(reader) as T[]);
            else
                list = new List<T>(_listJsonSerializer.ReadObject(reader) as T[]);

            return list;
        }

        internal static XmlSchema GetSchema(string baseUri, string schemaId)
        {
            XmlSchemaSet schemaSet;

            EnsureSchemaInit();

            if (!_baseUriSchemaSet.TryGetValue(baseUri, out schemaSet))
            {
                schemaSet = new XmlSchemaSet();
                foreach( XmlSchema schema in _schemas.Schemas() )
                {
                    MemoryStream memStream = new MemoryStream();
                    schema.Write(memStream);
                    memStream.Position = 0;
                    schemaSet.Add(XmlSchema.Read(memStream, null));
                }
                schemaSet.Compile();
                FixSchemaLocations(schemaSet, baseUri);
                _baseUriSchemaSet.Add(baseUri, schemaSet);
            }

            if (string.IsNullOrEmpty(schemaId))
            {
                foreach (XmlSchema schema in schemaSet.Schemas(_primarySchemaTns))
                {
                    return schema;
                }
            }
            else
            {
                string tns;
                if (_schemaQueries.TryGetValue(schemaId, out tns))
                {
                    foreach (XmlSchema schema in schemaSet.Schemas(tns))
                    {
                        return schema;
                    }
                }
            }
            return null;
        }
               

        private static void FixSchemaLocations(XmlSchemaSet schemaSet, string baseUri)
        {
            foreach (XmlSchema schema in schemaSet.Schemas())
            {
                foreach (XmlSchemaExternal external in schema.Includes)
                {
                    if (external != null && string.IsNullOrEmpty(external.SchemaLocation))
                    {
                        string namespaceUri = (external is XmlSchemaImport) ? ((XmlSchemaImport)external).Namespace : schema.TargetNamespace;
                        foreach (XmlSchema otherSchema in schemaSet.Schemas(namespaceUri ?? string.Empty))
                        {
                            if (otherSchema != schema)
                            {
                                external.SchemaLocation = baseUri + "?xsd="+_schemaNames[namespaceUri];
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
}

